﻿// RankingSejmowy 1.0 - Polish parliment statistics for the masses.
// Copyright (C) 2010 Jarosław Dubrownik
//  
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//  
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//  
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Iesi.Collections.Generic;
using RankingSejmowy.Model;

namespace RankingSejmowy.BLL.Import.Mappers
{
    public class BetterDiffMapper : IDiffMapper
    {
        #region IDiffMapper Members

        public void MapCadence(Cadence databaseCadence, Cadence importedCadence)
        {
            Debug.Assert(databaseCadence != null);
            Debug.Assert(importedCadence != null);

            RemoveOldDeputyVotes(databaseCadence);
            MapPartyCadences(databaseCadence, importedCadence);
            MapDeputies(databaseCadence, importedCadence);
            MapMeetings(databaseCadence, importedCadence);
        }

        #endregion

        private void RemoveOldDeputyVotes(Cadence databaseCadence)
        {
            //databaseCadence.Meetings.Select(x => )
        }

        private static void MapDeputies(Cadence databaseCadence, Cadence importedCadence)
        {
            Debug.Assert(databaseCadence != null);
            Debug.Assert(importedCadence != null);

            var importedParties = importedCadence.PartyCadences.Select(x => x.Party);
            var dbParties = databaseCadence.PartyCadences.Select(x => x.Party);
            var dbDeputies = databaseCadence.PartyCadences.SelectMany(x => x.Deputies);


            var importedPartyDeputies =
                importedParties.ToDictionary(x => x.PartyCadences.Single().Party.Name.ToLowerInvariant(),
                                             x =>
                                             new {x.PartyCadences.Single().Party, x.PartyCadences.Single().Deputies});

            foreach (var dbParty in dbParties)
            {
                var dbPartyName = dbParty.Name.ToLowerInvariant();
                if (!importedPartyDeputies.ContainsKey(dbPartyName))
                {
                    continue;
                }

                var importedPartyDeputy = importedPartyDeputies[dbPartyName];
                var importedDeputies = importedPartyDeputy.Deputies;

                var newDeputies =
                    importedDeputies.Except(dbDeputies, new DeputyComparer()).Select(x => new Deputy {Name = x.Name});

                var fromOtherPartyDeputies =
                    dbDeputies.Intersect(importedDeputies, new DeputyComparer()).Except(newDeputies,
                                                                                        new DeputyComparer());
                dbParty.PartyCadences.Single().AddDeputies(newDeputies.Concat(fromOtherPartyDeputies));
            }
        }

        private static void MapMeetings(Cadence databaseCadence, Cadence importedCadence)
        {
            Debug.Assert(databaseCadence != null);
            Debug.Assert(importedCadence != null);

            var importedMeetings = importedCadence.Meetings.Except(databaseCadence.Meetings, new MeetingComparer());
            foreach (var importedMeeting in importedMeetings)
            {
                var newMeeting = CreateMeeting(importedMeeting, databaseCadence);
                databaseCadence.AddMeeting(newMeeting);
            }
        }

        private static Meeting CreateMeeting(Meeting importedMeeting, Cadence databaseCadence)
        {
            Debug.Assert(databaseCadence != null);
            Debug.Assert(importedMeeting != null);

            var meeting = new Meeting {MeetingNum = importedMeeting.MeetingNum, Date = importedMeeting.Date};

            var newVotings =
                importedMeeting.Votings.Select(importedVoting => CreateVoting(importedVoting, databaseCadence));
            foreach (var newVoting in newVotings)
            {
                meeting.AddVoting(newVoting);
            }

            return meeting;
        }

        private static Voting CreateVoting(Voting importedVoting, Cadence databaseCadence)
        {
            Debug.Assert(databaseCadence != null);
            Debug.Assert(importedVoting != null);

            var voting = new Voting
                             {
                                 VotingNum = importedVoting.VotingNum,
                                 Date = importedVoting.Date,
                                 Subject = importedVoting.Subject,
                                 Description = importedVoting.Description
                             };

            var newDeputyVotes = CreateDeputyVotes(importedVoting.DeputiesVotes, databaseCadence);
            voting.AddDeputyVotes(newDeputyVotes);
            return voting;
        }

        private static DeputyVote[] CreateDeputyVotes(IEnumerable<DeputyVote> importedDeputyVotes,
                                                      Cadence databaseCadence)
        {
            Debug.Assert(importedDeputyVotes != null);
            Debug.Assert(importedDeputyVotes.Any());
            Debug.Assert(databaseCadence != null);

            var partyCadencesDict = databaseCadence.PartyCadences.ToDictionary(x => x.Party.Name,
                                                                               x => new
                                                                                        {
                                                                                            PartyCadence = x,
                                                                                            DeputiesDict =
                                                                                        x.Deputies.ToDictionary(
                                                                                            y => y.Name, y => y)
                                                                                        });


            var list = new List<DeputyVote>();
            foreach (var importedDeputyVote in importedDeputyVotes)
            {
                var partyName = importedDeputyVote.PartyCadence.Party.Name;
                if (!partyCadencesDict.ContainsKey(partyName))
                {
                    throw new InvalidOperationException(string.Format("No such party: {0}.", partyName));
                }

                var pc = partyCadencesDict[partyName];

                var newDeputyVote = new DeputyVote {Vote = importedDeputyVote.Vote};

                var deputyName = importedDeputyVote.Deputy.Name;
                if (!pc.DeputiesDict.ContainsKey(deputyName))
                {
                    throw new InvalidOperationException(string.Format("No such deputy: {0}", deputyName));
                }

                var deputy = pc.DeputiesDict[deputyName];
                deputy.AddDeputyVote(newDeputyVote, pc.PartyCadence);
                list.Add(newDeputyVote);
            }

            Debug.Assert(importedDeputyVotes.Count() == list.Count);
            return list.ToArray();
        }

        private static void MapPartyCadences(Cadence databaseCadence, Cadence importedCadence)
        {
            Debug.Assert(databaseCadence != null);
            Debug.Assert(importedCadence != null);

            var dbParties = databaseCadence.PartyCadences.Select(x => x.Party);
            var importParites = importedCadence.PartyCadences.Select(x => x.Party);

            var parties = importParites.Except(dbParties, new PartyComparer());

            if (!parties.Any())
            {
                return;
            }

            var partyDeputies = parties.ToDictionary(x => x.PartyCadences.Single().Party.Name.ToLowerInvariant(),
                                                     x => x.PartyCadences.Single().Deputies);
            var partyCadences = parties.Select(x => CreatePartyCadence(x, partyDeputies, databaseCadence));
            databaseCadence.AddPartyCadence(partyCadences);
        }

        private static PartyCadence CreatePartyCadence(Party party, Dictionary<string, ISet<Deputy>> partyDeputies,
                                                       Cadence databaseCadence)
        {
            Debug.Assert(party != null);
            Debug.Assert(partyDeputies != null);
            Debug.Assert(databaseCadence != null);

            var pc = new PartyCadence();
            var deputies = partyDeputies[party.Name.ToLowerInvariant()];
            pc.AddDeputies(ConcatPartyDeputies(deputies, databaseCadence));
            var p = new Party {Name = party.Name};
            p.AddPartyCadence(pc);
            return pc;
        }

        private static IEnumerable<Deputy> ConcatPartyDeputies(IEnumerable<Deputy> importedDeputies,
                                                               Cadence databaseCadence)
        {
            Debug.Assert(importedDeputies != null);
            Debug.Assert(importedDeputies.Any());
            Debug.Assert(databaseCadence != null);

            var allDeputies = databaseCadence.PartyCadences.SelectMany(x => x.Deputies);
            var existingDeputies = allDeputies.Intersect(importedDeputies, new DeputyComparer());
            var newDeputies = importedDeputies.Except(existingDeputies, new DeputyComparer());
            return existingDeputies.Concat(newDeputies);
        }
    }
}